#ifndef _FIO66_UTILS_H_
#define _FIO66_UTILS_H_

#include "src/base/fio66_logger.h"

#include <fstream>
#include <optional>
#include <string_view>
#include <filesystem>

#include <cereal/cereal.hpp>
#include <cereal/archives/json.hpp>

namespace fio66::utils {
#define ALLOW_COPY_AND_ASSIGN(ClassName)            \
  ClassName() = default;                 \
  ~ClassName() = default;                

#define DISALLOW_COPY_AND_ASSIGN(ClassName) \
  ClassName(const ClassName&) = delete;      \
  void operator=(const ClassName&) = delete;

template <class T, size_t N>
constexpr size_t ArraySize(T (&)[N]) {
  return N;
}
#define _countof(T) ArraySize(T)

#define ZeroMemory(T) memset(&T, 0, sizeof(T));

std::string GetExePath();
std::string GetExeDir();

bool PathFileExsist(std::string_view path);

bool AppendFile(std::ofstream* des_file, std::string_view src_file_name);
bool AppendFile(std::ofstream* des_file, std::ifstream* src_file);

std::string GetFormatTime();

bool DeleteFile(std::string_view delte_file);

template <typename T>
bool InRange(const T& value, const T& low, const T& high) {
  return (value > low) && (value < high);
}

std::optional<std::string> ReadFile(std::string_view file_name);

bool WriteFile(std::string_view file_name, std::string_view data);

bool WriteFile(std::string_view file_name, const char* data, const uint32_t size);

std::vector<std::filesystem::directory_entry> GetDirectoryFiles(std::string_view dir_name);

enum PathAccessRight{
  kAccessRightExist = 0,
  kAccessRightRead,
  kAccessRightWrite,
  kAccessRightExe
};

bool CheckPathAccessRight(std::string_view path, const PathAccessRight mode);

template <class T>
inline std::string SerializeJson(const T& raw_data, std::string_view root_name = "root") {
  std::ostringstream ostring_stream;
  {
    cereal::JSONOutputArchive out(ostring_stream);
    out(cereal::make_nvp(root_name, raw_data));
  }
  return ostring_stream.str();
}

template<class T>
T DeserializeJson(std::string_view serialized_data, std::string_view root_name = "root"){
  T temp;
  if (!serialized_data.empty()) {
    std::istringstream istring_stream(serialized_data.data());
    cereal::JSONInputArchive in(istring_stream);
    in(cereal::make_nvp(root_name.data(), temp));
  }
  return temp;
}

template <typename T>
std::optional<T> GetJSONObjFromFile(std::string_view file_path, std::string_view key) {
  std::ifstream json_file(file_path.data());
  if (!json_file.is_open()) {
    LOGE << "Can not read file:" << file_path;
    return std::nullopt;
  }
  try {
    std::stringstream ss;
    ss << json_file.rdbuf();
    return utils::DeserializeJson<T>(ss.str(), key);
  } catch (const std::exception& e) {
    LOGE <<"Catch exception:" << e.what();
  } catch (...) {
    LOGE <<"Catch some exception!";
  }
  return std::nullopt;
}

// Use implicit_cast as a safe version of static_cast or const_cast
// for upcasting in the type hierarchy (i.e. casting a pointer to Foo
// to a pointer to SuperclassOfFoo or casting a pointer to Foo to
// a const pointer to Foo).
// When you use implicit_cast, the compiler checks that the cast is safe.
// Such explicit implicit_casts are necessary in surprisingly many
// situations where C++ demands an exact type match instead of an
// argument type convertable to a target type.
//
// The From type can be inferred, so the preferred syntax for using
// implicit_cast is the same as for static_cast etc.:
//
//   implicit_cast<ToType>(expr)
//
// implicit_cast would have been part of the C++ standard library,
// but the proposal was submitted too late.  It will probably make
// its way into the language in the future.
template<typename To, typename From>
inline To implicit_cast(From const &f)
{
  return f;
}
}  // namespace fio66::utils

#endif  // _FIO66_UTILS_H_
